﻿using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO.IsolatedStorage;
using System.Threading.Tasks;
#if NETFX_CORE
using Windows.UI.Xaml;
#else
using System.Windows;
#endif

namespace DaveSexton.Labs
{
#if NETFX_CORE
	public abstract partial class WindowsStoreLab
#elif WINDOWS_PHONE
	public abstract partial class PhoneLab
#elif SILVERLIGHT
	public abstract partial class SilverlightLab
#else
	public abstract partial class WindowsLab
#endif
	{
		#region Public Properties
		public bool IsInDesignMode
		{
			get
			{
#if SILVERLIGHT || NETFX_CORE
				return PlatformRuntime.IsInDesignMode();
#else
				return PlatformRuntime.IsInDesignMode(this);
#endif
			}
		}

		public string DisplayName
		{
			get
			{
				Contract.Ensures(Contract.Result<string>() != null);

				return (string)GetValue(DisplayNameProperty) ?? string.Empty;
			}
			set
			{
				SetValue(DisplayNameProperty, value);
			}
		}

		public string FullCategory
		{
			get
			{
				Contract.Ensures(Contract.Result<string>() != null);

				return (string)GetValue(FullCategoryProperty) ?? string.Empty;
			}
			set
			{
				SetValue(FullCategoryProperty, value);
			}
		}

		public string Description
		{
			get
			{
				Contract.Ensures(Contract.Result<string>() != null);

				return (string)GetValue(DescriptionProperty) ?? string.Empty;
			}
			set
			{
				SetValue(DescriptionProperty, value);
			}
		}

		public bool IsLabEnabled
		{
			get
			{
				return !HasProxy || proxy.IsEnabled;
			}
			set
			{
				if (!HasProxy)
				{
					throw new InvalidOperationException();
				}

				proxy.IsEnabled = value;
			}
		}

		bool ILab.IsEnabled
		{
			get
			{
				return IsLabEnabled;
			}
			set
			{
				IsLabEnabled = value;
			}
		}

		public virtual string SourceCode
		{
			get
			{
				// null indicates that the proxy should provide the source
				return null;
			}
		}

		public virtual string SourceXaml
		{
			get
			{
				// null indicates that the proxy should provide the source
				return null;
			}
		}

		public Lab Proxy
		{
			get
			{
				Contract.Ensures(Contract.Result<Lab>() == proxy);

				return proxy;
			}
			set
			{
				proxy = value;

				DisplayName = proxy.DisplayName;
				FullCategory = proxy.FullCategory;
				Description = proxy.Description;

				ProxyAssigned();
			}
		}

		public bool HasProxy
		{
			get
			{
				Contract.Ensures(Contract.Result<bool>() == (Proxy != null));

				return proxy != null;
			}
		}

		bool ILab.IsProxy
		{
			get
			{
				Contract.Ensures(!Contract.Result<bool>());

				return false;
			}
		}

		ILab ILab.ActualLab
		{
			get
			{
				Contract.Ensures(Contract.Result<ILab>() == this);

				return this;
			}
		}
		#endregion

		#region Private / Protected
		protected internal ILabHost Host
		{
			get;
			set;
		}

		protected TraceListener Listener
		{
			get
			{
				Contract.Ensures(Contract.Result<TraceListener>() != null);

				var value = listener.Value;

				Contract.Assume(value != null);

				return value;
			}
		}

		protected static IsolatedStorageFile Storage
		{
			get
			{
				Contract.Ensures(Contract.Result<IsolatedStorageFile>() != null);

				return Lab.Storage;
			}
		}

		protected string StorageDirectory
		{
			get
			{
				Contract.Requires(HasProxy);
#if !WINDOWS_PHONE_70
				Contract.Ensures(!string.IsNullOrWhiteSpace(Contract.Result<string>()));
#else
				Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
#endif

				return Proxy.StorageDirectory;
			}
		}

#if SILVERLIGHT
		private static System.Windows.Threading.Dispatcher dispatcher;
#endif
		private readonly Lazy<TraceListener> listener;
		private readonly bool shouldTraceHeader;
		private Lab proxy;
		#endregion

		#region Methods
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(listener != null);
			Contract.Invariant(!((ILab)this).IsProxy);
			Contract.Invariant(proxy == null || proxy.ActualLab == this);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
			Justification = "This is a factory method.")]
		private TraceListener CreateListener()
		{
			Contract.Ensures(Contract.Result<TraceListener>() != null);

			return new AnonymousTraceListener(
				message => Trace(message),
				message => TraceLine(message),
				(cache, source, eventType, id, message) => TraceEvent(eventType, message));
		}

		/// <summary>
		/// Formats the specified source code.
		/// </summary>
		/// <remarks>
		/// If you override the <see cref="SourceCode"/> property and do not call the base implementation, 
		/// then <see cref="FormatSourceCode"/> will not be called.
		/// </remarks>
		/// <param name="source">The source code to be formatted.</param>
		/// <returns>Formatted source code or <see langword="null"/> to indicate that the default 
		/// formatting should be used.</returns>
		public virtual string FormatSourceCode(string source)
		{
			// null indicates that the proxy should format the source
			return null;
		}

		/// <summary>
		/// Formats the specified XAML.
		/// </summary>
		/// <remarks>
		/// If you override the <see cref="SourceXaml"/> property and do not call the base implementation, 
		/// then <see cref="FormatSourceXaml"/> will not be called.
		/// </remarks>
		/// <param name="source">The XAML to be formatted.</param>
		/// <returns>Formatted XAML or <see langword="null"/> to indicate that the default 
		/// formatting should be used.</returns>
		public virtual string FormatSourceXaml(string source)
		{
			// null indicates that the proxy should format the source
			return null;
		}

		private void TraceHeader()
		{
			Contract.Requires(HasProxy);

			var name = proxy.OrganizedName;

			TraceLine();
			TraceTitle(name);

			if (Description.Length > 0)
			{
				TraceTitle(Description);
			}

			TraceLine();
		}

		protected virtual void ProxyAssigned()
		{
			Contract.Requires(HasProxy);

			// for derived classes
		}

		public static async Task EnsureUIThreadAccessAsync()
		{
			Contract.Ensures(Contract.Result<Task>() != null);

#if NETFX_CORE
			var window = Window.Current;

			if (window != null && !window.Dispatcher.HasThreadAccess)
			{
				await window.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { });
			}
#elif SILVERLIGHT
			if (dispatcher != null && !dispatcher.CheckAccess())
			{
				await dispatcher.InvokeAsync(() => { });
			}
#else
			var app = Application.Current;

			if (app != null && !app.Dispatcher.CheckAccess())
			{
				await app.Dispatcher.InvokeAsync(() => { });
			}
#endif
		}
		#endregion

		#region Event Handlers
		private void GUILab_Loaded(object sender, RoutedEventArgs e)
		{
			if (!IsInDesignMode)
			{
				Contract.Assume(HasProxy);

				if (shouldTraceHeader)
				{
					TraceHeader();
				}
			}

#if !SILVERLIGHT || SILVERLIGHT_4 || WINDOWS_PHONE
			Contract.Assume(proxy == null || proxy.ActualLab == this);
#endif

#if SILVERLIGHT
			if (dispatcher == null)
			{
				dispatcher = ((DependencyObject)sender).Dispatcher;
			}
#endif
		}
		#endregion
	}
}